﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Globalization;

namespace ValidationFramework.Reflection
{
    public static class TypeExtensions
    {
        /// <summary>
        /// Gets a value indicating whether property with the given name exists in the current type.
        /// </summary>
        /// <param name="self">Object being extended</param>
        /// <param name="propertyName">Name of property to check for</param>
        /// <returns>True if a property is</returns>
        /// <remarks>Uses reflection to determin the existance of the property. Case is ignored.</remarks>
        public static bool ContainsProperty(this Type self, string propertyName)
        {
            return self.GetProperties().Count(x => x.Name.ToLower() == propertyName.ToLower()) > 0;
        }

        // --- Methods

        /// <summary>
        /// Gets a friendly name for a <see cref="Type"/>.
        /// </summary>
        /// <param name="type">The <see cref="Type"/> to get the friendly name for.</param>
        /// <returns>A friendly name for a <paramref name="type"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="type"/> is null.</exception>
        public static string ToUserFriendlyString(this Type type)
        {
            Guard.ArgumentNotNull(type, "type");
            var stringBuilder = new StringBuilder(TrimGenericText(type));
            var arguments = type.GetGenericArguments();
            if (arguments.Length > 0)
            {
                stringBuilder.Append("<");
                foreach (var genericArgument in arguments)
                {
                    stringBuilder.Append(ToUserFriendlyString(genericArgument));
                    stringBuilder.Append(",");
                }
                stringBuilder.Remove(stringBuilder.Length - 1, 1);
                stringBuilder.Append(">");
            }
            return stringBuilder.ToString();
        }

        internal static bool IsEnumDefined(this Type enumType, object value, bool ignoreCase)
        {
            var valueTypeHandle = value.GetType().TypeHandle;
            if (valueTypeHandle.Equals(TypePointers.StringTypeHandle))
            {
                var valueAsString = (string)value;
                if (valueAsString.Length == 0)
                {
                    return true;
                }
#if (WindowsCE || SILVERLIGHT)
				var  names = EnumExtensions.GetNames(enumType);
#else
                var names = Enum.GetNames(enumType);
#endif
                if (ignoreCase)
                {
                    var toUpper = valueAsString.ToUpper();
                    for (var index = 0; index < names.Length; index++)
                    {
                        var name = names[index];
                        if (name.ToUpper() == toUpper)
                        {
                            return true;
                        }
                    }
                }
                else
                {
                    for (var index = 0; index < names.Length; index++)
                    {
                        var name = names[index];
                        if (name == valueAsString)
                        {
                            return true;
                        }
                    }
                }
            }
            else
            {
                var valueAsLong = Convert.ToInt64(value, CultureInfo.InvariantCulture);
                if (valueAsLong < 0)
                {
                    return false;
                }
#if (WindowsCE || SILVERLIGHT)
               var values = EnumExtensions.GetValues(enumType);
#else
                var values = Enum.GetValues(enumType);
#endif
                foreach (var enumValue in values)
                {
                    if (valueAsLong == Convert.ToInt64(enumValue, CultureInfo.InvariantCulture))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static string TrimGenericText(this Type type)
        {
            if (type.FullName != null)
            {
                var fullName = type.FullName;
                var indexOfQuote = fullName.IndexOf("`");
                if (indexOfQuote > 0)
                {
                    fullName = fullName.Substring(0, indexOfQuote);
                }
                return fullName;
            }
            else
            {
                return type.Name;
            }
        }


        internal static bool IsOutParam(this ParameterInfo parameterInfo)
        {
            return ((parameterInfo.Attributes & ParameterAttributes.Out) != ParameterAttributes.None);
        }

        /// <summary>
        /// Determines whether the <see cref="Type"/> represented by the <paramref name="typeToCheck"/> derives from the <see cref="Type"/> represented by the <paramref name="baseType"/>. 
        /// </summary>
        /// <param name="typeToCheck">The child <see cref="Type"/> to check for.</param>
        /// <param name="baseType">The base <see cref="Type"/> to check for.</param>
        /// <param name="processGenericTypeDefinition"><see langword="true"/> to process the GenericTypeDefinition for each interface and base class.</param>
        /// <exception cref="ArgumentNullException"><paramref name="typeToCheck"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="baseType"/> is null.</exception>
        /// <returns>true if the <paramref name="typeToCheck"/> derives from <paramref name="baseType"/> or if they are the same type; otherwise, false.</returns>
        public static bool IsAssignableFrom(this Type baseType, Type typeToCheck, bool processGenericTypeDefinition)
        {
            Guard.ArgumentNotNull(typeToCheck, "typeToCheck");
            Guard.ArgumentNotNull(baseType, "baseType");

            if (baseType.IsAssignableFrom(typeToCheck))
            {
                return true;
            }
            if (processGenericTypeDefinition)
            {
                for (var index = 0; index < typeToCheck.GetInterfaces().Length; index++)
                {
                    var interfaceType = typeToCheck.GetInterfaces()[index];
                    if (interfaceType.IsGenericType && (interfaceType.GetGenericTypeDefinition() == baseType))
                    {
                        return true;
                    }
                }
                while (typeToCheck != null)
                {
                    if (typeToCheck.IsGenericType && (typeToCheck.GetGenericTypeDefinition() == baseType))
                    {
                        return true;
                    }
                    typeToCheck = typeToCheck.BaseType;
                }
            }

            return false;
        }

        internal static object GetStaticProperty(string typeName, string propertyName)
        {

            if ((typeName == null) && (propertyName == null))
            {
                return null;
            }
            else if ((typeName != null) && (propertyName != null))
            {
                var type = Type.GetType(typeName, true);
                return GetStaticProperty(type, propertyName);
            }
            else
            {
                throw new InvalidOperationException("Both typeName and propertyName have to be null or not null.");
            }
        }

        internal static object GetStaticProperty(Type type, string propertyName)
        {
            var propertyInfo = type.GetProperty(propertyName, BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public);
            return propertyInfo.GetValue(null, null);
        }

        /// <summary>
        /// Determine if a <see cref="MethodBase"/> represents a 'get' or 'set' method.
        /// </summary>
        /// <param name="methodBase">The <see cref="MethodBase"/> to check.</param>
        /// <returns><c>true</c> if <see cref="MethodBase"/> represents a 'get' or 'set' method; otherwise <c>false</c></returns>
        internal static bool IsPropertyMethod(MethodBase methodBase)
        {
            if (methodBase.Name.StartsWith("set_") || methodBase.Name.StartsWith("get_"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        internal static MethodBase GetBaseMethod(this MethodBase methodBase)
        {
            var type = methodBase.DeclaringType;
            var baseType = type.BaseType;
            if (baseType == null)
            {
                return null;
            }
            const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
            var list = new List<Type>();
            foreach (var parameterInfo in methodBase.GetParameters())
            {
                list.Add(parameterInfo.ParameterType);
                //TODO: param modifiers
            }
            return baseType.GetMethod(methodBase.Name, bindingFlags, null, list.ToArray(), null);
        }



        // --- IsNullable Extenions
        public static bool IsNullable(this Type self)
        {
            return self.IsGenericType && self.GetGenericTypeDefinition().TypeHandle.Equals(TypePointers.NullableTypeHandle);
        }



        // --- Explicit Property Extensions


        /// <summary>
        /// Determins wether this property is explicitly implementing an interface
        /// </summary>
        /// <param name="self"></param>
        /// <param name="interfaceToCheck"></param>
        /// <returns></returns>
        /// <remarks>To avoid the performance hit this method assumes the declaring type of the property actually
        /// implements the interfaceToCheck.</remarks>
        public static bool IsExplicitProperty(this Type self, string propertyName)
        {
            if (self == null)
                return false;

            // if the type itself is an interface assume its not explcit
            if (self.IsInterface)
                return false;

            foreach (var iFace in self.GetInterfaces())
            {
                if (IsExplicitProperty(self, propertyName, iFace))
                    return true;
            }
            // if we got this far, its not an explicit property
            return false;
        }

        // TODO: Consider exposing this so consumers can check for explicit properties without looping over all the interfaces.
        /// <summary>
        /// Determins wether this property is explicitly implementing an interface
        /// </summary>
        /// <param name="self"></param>
        /// <param name="interfaceToCheck"></param>
        /// <returns></returns>
        /// <remarks>To avoid the performance hit this method assumes the declaring type of the property actually
        /// implements the interfaceToCheck. It will throw an opaque exception if it doesn't.</remarks>
        internal static bool IsExplicitProperty(this Type self, string propertyName, Type interfaceToCheck)
        {
            // if type itself is an interface assume its not explcit
            if (self.IsInterface)
                return false;

            // TODO: decide wether we should take the performance his and actually check this.
            //// ensure we only continue if this type acctually implements the interfaceToCheck
            //var implementsInterface = declaringType.GetInterfaces().SingleOrDefault(x => x.Name == interfaceToCheck.Name) != null;
            //if (!implementsInterface)
            //    return false;

            // Get the details of the interface implementation mapping.
            var map = self.GetInterfaceMap(interfaceToCheck);

            // check to see if type explicitly implementes the property
            var propertyGetOrSetMethod = map.TargetMethods.FirstOrDefault(x =>
                    x.Name.EndsWith("get_" + propertyName) ||
                    x.Name.EndsWith("set_" + propertyName));

            if (propertyGetOrSetMethod == null)
                return false;

            return IsExplicitPropertyMethod(propertyGetOrSetMethod);
        }

        /// <summary>
        /// Checks to see if the supplied methodInfo is explicitly defined
        /// </summary>
        /// <param name="propertyGetOrSetMethod"></param>
        /// <returns>returns true when the supplied methodInfo is explicitly defined</returns>
        private static bool IsExplicitPropertyMethod(MethodInfo propertyGetOrSetMethod)
        {
            return propertyGetOrSetMethod.IsFinal && propertyGetOrSetMethod.IsPrivate;
        }

        public static List<MethodInfo> GetExplicitProperties(this Type type)
        {
            var methods = new List<MethodInfo>();
            var interfaces = type.GetInterfaces();

            foreach (var iface in interfaces)
            {
                var map = type.GetInterfaceMap(iface);
                foreach (var method in map.TargetMethods)
                {
                    if (method.IsPrivate && method.MemberType == MemberTypes.Property)
                        methods.Add(method);
                }
            }
            return methods;
        }



        // --- Constructor Extensions    
        internal static ConstructorInfo GetPublicInstanceConstructor(this Type type, params Type[] types)
        {
            return type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, types, null);
        }

        internal static object CreateGenericInstance(this Type self, Type typeParam, object[] constructorParams)
        {
            return GenericTypeFactory.CreateInstance(self, typeParam, constructorParams);
        }
        //internal static string GetConsructorSignature(Type constructorForType, Type[] paramTypes)
        //{
        //    var builder = new StringBuilder();
        //    builder.Append(constructorForType.Name);
        //    builder.Append("(");
        //    var counter = 0;
        //    foreach (var paramType in paramTypes)
        //    {
        //        if (counter > 0)
        //        {
        //            builder.Append(",");
        //        }

        //        builder.Append(paramType.Name);
        //        counter += 1;
        //    }
        //    builder.Append(")");
        //    return builder.ToString();
        //}
    }
}
